package com.tzy.for10evalue.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

@Slf4j
@Data
public class ExcelUtil {
    private WriteSheet sheet;
    private ExcelWriter writer;
    private ServletOutputStream out = null;
    private HttpServletResponse response;
    private String fileName;

    /**
     * 一次性读取excel数据，同步返回[easyExcel官网不推荐使用，因为数据放到内存，大数据量会引发OOM]
     * 大数据量使用：
     *
     * @param file  MultipartFile
     * @param clazz 解析excel的模型
     * @return List 数据list<E>
     * @see NewExcelUtil#LineByLineRead(MultipartFile, Class, com.alibaba.excel.event.AnalysisEventListener)
     */
    @SneakyThrows
    public static <E> List<E> OnceRead(MultipartFile file, Class<E> clazz) {
        Assert.notNull(file, "file不可为空!");
        Assert.notNull(clazz, "class不可为空!");
        checkShuffixExcel(file);
        // 这里 需要指定读用哪个class去读，然后读取第一个sheet 同步读取会自动finish
        List<E> list = EasyExcel.read(file.getInputStream()).head(clazz).sheet().doReadSync();
        return list;
    }

    /**
     * easyExcel官网推荐使用（逐行读取）
     * 官网案例见：https://www.yuque.com/easyexcel/doc/read#8e72a573
     *
     * @param file     MultipartFile
     * @param clazz    解析excel的模型
     * @param listener 继承AnalysisEventListener并重写invoke，传入的listener需new出来的对象，详细见官网案例
     */
    @SneakyThrows
    public static <E> void LineByLineRead(MultipartFile file, Class<E> clazz, AnalysisEventListener<E> listener) {
        Assert.notNull(file, "file不可为空!");
        Assert.notNull(clazz, "class不可为空!");
        checkShuffixExcel(file);
        // 这里 需要指定读用哪个class去读，然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(file.getInputStream(), clazz, listener).sheet().doRead();
    }

    /**
     * 一次导出静态方法（只可在小数据量[5万]场景下使用，大数据量使用下个方法）
     * 大数据量使用：
     *
     * @param response HttpServletResponse
     * @param fileName 文件名
     * @param list     数据list<E>
     * @param clazz    数据模型class
     * @see NewExcelUtil#NewExcelUtil(HttpServletResponse, String, Class)
     */
    public static <E> void OnceWrite(HttpServletResponse response, String fileName, List<E> list, Class<E> clazz) {
        Assert.notNull(response, "response不可为空!");
        Assert.notNull(fileName, "文件名fileName不可为空!");
        Assert.notNull(list, "数据list不可为空!");
        Assert.notNull(clazz, "class不可为空!");
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            ExcelWriter writer = EasyExcel.write(out, clazz).build();
            WriteSheet sheet = EasyExcel.writerSheet("sheet1").build();
            writer.write(list, sheet);
            response.reset();
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode((fileName + System.currentTimeMillis() + ".xlsx"), "UTF-8"));
            writer.finish();
            out.flush();
        } catch (Exception e) {
            log.error("生成excel失败,msg={}", e.getMessage());
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    /**
     * 循环导出excel对象,如果一次性导出请调用 OnceWrite 静态方法
     * 此方法分3个步骤，示例见 http://zentao.delib2b.com/zentao/doc-view-841.html
     * 1.初始化对象 excel = new NewExcelUtil(HttpServletResponse response,String fileName, Class<? extends BaseRowModel> ObjClass)
     * 2.循环内执行 excel.writerData(List list)
     * 3.完成 excel.finish()
     *
     * @param response     HttpServletResponse
     * @param fileName     文件名
     * @param headObjClass excel模型class
     */
    public ExcelUtil(HttpServletResponse response, String fileName, Class headObjClass) {
        try {
            this.response = response;
            this.out = response.getOutputStream();
            this.fileName = fileName;
            this.writer = EasyExcel.write(this.out, headObjClass).build();
            this.sheet = EasyExcel.writerSheet("sheet1").build();
        } catch (Exception e) {
            try {
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 写入数据
     *
     * @param list 数据list
     */
    public void writerData(List list) {
        Assert.notNull(list, "数据不可为空!");
        writer.write(list, sheet);
    }

    /**
     * 完成excel写入输出文件流
     */
    public void finish() {
        try {
            // 下载EXCEL
            response.reset();
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode((fileName + System.currentTimeMillis() + ".xlsx"), "UTF-8"));
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            writer.finish();
            out.flush();
            out.close();
        } catch (IOException e) {
            log.error("生成excel失败,msg={}", e.getMessage());
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e2) {
                    e2.printStackTrace();
                }

            }

        }
    }

    /**
     * 校验文件后缀名是否为excel
     */
    private static void checkShuffixExcel(MultipartFile file) {
        Assert.notNull(file, "file不可为空!");
        String originalFilename = file.getOriginalFilename();
        String suffixName = originalFilename.substring(originalFilename.lastIndexOf("."));
        Assert.notNull(suffixName, "文件后缀名为空！");
        suffixName = suffixName.toLowerCase();
        Boolean isExcel = ExcelTypeEnum.XLS.getValue().equals(suffixName) || ExcelTypeEnum.XLSX.getValue().equals(suffixName);
        Assert.isTrue(isExcel, "文件后缀名不为excel!");
    }

}
